<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MVVM</title>
</head>

<body>
    <div id="app">
        <h1>{{name}}哈哈哈{{age}}</h1>
        <input type="text" v-model="name">
        <h1>{{age}}</h1>
        <input type="text" v-model="age">
    </div>
    <script>

        function Vue(option) {
            this.$el = document.querySelector(option.el)
            this.$data = option.data
            observe(this.$data)//数据绑定
            nodeToFragment(this.$el, this)//模板编译
        }

        function observe(data) {
            if (typeof data != 'object') return
            Object.keys(data).forEach((key) => {
                defineReactive(data, key, data[key])
            })
        }

        function defineReactive(data, key, value) {
            // debugger
            observe(value)
            let dep = new Dep();
            Object.defineProperty(data, key, {
                get() {
                    if (Dep.target) {
                        dep.addSub(Dep.target)
                    }
                    return value
                },
                set(newVal) {
                    if (value !== newVal) {
                        value = newVal
                        dep.notify()
                    }

                }
            })
        }

        function nodeToFragment(el, vm) {
            let fragment = document.createDocumentFragment()

            let child;
            while (child = el.firstChild) {
                fragment.appendChild(child)
                compile(child, vm)
            }
            // debugger
            el.appendChild(fragment)
        }

        function compile(node, vm) {
            if (node.nodeType == 1) {
                // 元素节点
                let attrs = node.attributes
                // console.log(attrs)
                Array.from(attrs).forEach(item => {
                    if (/^v-/.test(item.name)) {
                        let key = item.nodeValue
                        let val = vm.$data[key]
                        new Watcher(node, key, vm)
                        node.value = val
                        node.addEventListener('input', (e) => {
                            vm.$data[key] = e.target.value
                        })
                    }
                })

                Array.from(node.childNodes).forEach(child => {
                    compile(child, vm)
                })
            } else {
                // 文本节点
                let str = node.textContent;// 获取文本字符串
                let str2 = str;
                if (/\{\{(\w+)\}\}/.test(str)) {
                    str = str.replace(/\{\{(\w+)\}\}/g, (a, b) => {
                        new Watcher(node, b, vm, str2, (node, b, vm, st) => {
                            node.textContent = st.replace(/\{\{(\w+)\}\}/g, (a, b) => {
                                return vm.$data[b]
                            })
                        })
                        return vm.$data[b]
                    })
                    node.textContent = str
                }
            }
        }




        class Dep {
            constructor() {
                this.Subs = []
            }
            addSub(sub) {
                this.Subs.push(sub)
            }
            notify() {
                this.Subs.forEach((sub) => {
                    sub.update()
                })
            }
        }

        class Watcher {
            constructor(node, key, vm, str = '', cb = null) {
                Dep.target = this;
                this.node = node;
                this.key = key;
                this.vm = vm;
                this.cb = cb
                this.str = str
                this.value = vm.$data[key]
                Dep.target = null;
            }
            update() {
                if (this.node.nodeType == 1) {
                    // 就是input框
                    this.node.value = vm.$data[this.key];
                } else {
                    this.cb(this.node, this.key, this.vm, this.str)
                    // this.node.textContent = vm.$data[this.key];
                }
            }
        }

        const vm = new Vue({
            el: '#app',
            data: {
                name: '张景鹏',
                age: 24
            }
        })

    </script>
</body>

</html>